#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <chrono>

#include <esp_log.h>

#include "./c3log.h"
#include "oled/oled_drv_intf.h"
#include <hal/servers/websocket/WebSocketService.h>

#define MAX_LOG_FILE_CNT (5)
#define MAX_ID (100)

#define LOG_FILE_PRE "/spiffs/esp_sys"  // "/spiffs/esp_sys_x.log"

#define LOG_FILE_0 "esp_sys0.log"
#define LOG_FILE_1 "esp_sys1.log"

#define MAX_SIZE (100 * 1024)  // 100K
const int MAX_LOG_LEN = 128;
static CustomLog gCustomLog;
static bool gEnableWS_Log = true;
static WebSocketService* gpWebSocketServer = WebSocketService::getInstance();

double get_sys_secend() {
    static std::chrono::steady_clock::time_point clock_ZERO;
    static std::chrono::steady_clock::time_point cur_clock;
    static std::chrono::steady_clock::duration span;

    cur_clock = std::chrono::steady_clock::now();
    span = cur_clock - clock_ZERO;
    return double(span.count()) * std::chrono::steady_clock::period::num /
           std::chrono::steady_clock::period::den;
}

void log_tag(const char *color, const char *TAG, const char *ModuleTAG,
             const char *format, ...) {
    char logstr[MAX_LOG_LEN];
    snprintf(logstr, MAX_LOG_LEN, "%s%.03lf %s %s ", color, get_sys_secend(),
             TAG, ModuleTAG);

    va_list vlArgs;
    va_start(vlArgs, format);
    vsnprintf(logstr + strlen(logstr), MAX_LOG_LEN - strlen(logstr), format,
              vlArgs);
    va_end(vlArgs);
    fprintf(stderr, "%s\n", logstr);
    // oled_log(logstr);
    if (gCustomLog.mInited) {
        // log to file
        fprintf(gCustomLog.mpLogFD, "%s\n", logstr);
        gCustomLog.mLogSize += strlen(logstr);
        if (gCustomLog.mLogSize > MAX_SIZE) {
            // create new log file
            gCustomLog.createNewLogFile(gCustomLog.mContentId,
                                        gCustomLog.mNameId);
        }
    }

    // send log to webserver
    if (gEnableWS_Log) {
        gpWebSocketServer->postDataImmediately((uint8_t *)logstr, strlen(logstr));
    }

    return;
}

void log2file(const char *log_path, bool append, bool ts, const char *format,
              ...) {
    char logstr[MAX_LOG_LEN];
    double seconds = get_sys_secend();
    if (ts) {
        snprintf(logstr, MAX_LOG_LEN, "%.01lf ", seconds);
    }
    va_list vlArgs;
    va_start(vlArgs, format);
    vsnprintf(logstr + strlen(logstr), MAX_LOG_LEN - strlen(logstr), format,
              vlArgs);
    va_end(vlArgs);

    if (nullptr == log_path) {
        return;
    }

    FILE *logfd = NULL;
    if (append) {
        logfd = fopen(log_path, "aw+");
    } else {
        logfd = fopen(log_path, "w+");
    }

    if (logfd == NULL) {
        LOGI("log2file open %s failed", log_path);
        return;
    }
    fprintf(logfd, "%s\n", logstr);
    fclose(logfd);
}

void read_status(const char *log_path, char *buf, int size) {
    FILE *f = fopen(log_path, "r");
    if (f == NULL) {
        return;
    }
    fgets(buf, size, f);
    fclose(f);
}

void print_file(const char *log_path) {
    FILE *f = fopen(log_path, "r");
    if (f == NULL) {
        return;
    }
    char buf[256];
    while (fgets(buf, sizeof(buf), f) != NULL) printf("%s", buf);
    fclose(f);
}

int file_size(char *filename) {
    FILE *fp = fopen(filename, "r");
    if (!fp) return -1;
    fseek(fp, 0L, SEEK_END);
    int size = ftell(fp);
    fclose(fp);
    return size;
}

static char log_print_buffer[128];

// custom log redirect
int vprintf_redirect_custom(const char* szFormat, va_list args) {
    int len = vsnprintf(log_print_buffer, sizeof(log_print_buffer), szFormat, args);
    if (len > 0 && len < 128) {
        bool valid_char = false;
        for (int i = 0; i < len; i++) {
            if (log_print_buffer[i] == '\n') {
                log_print_buffer[i] = ' ';
            }
            if (log_print_buffer[i] > ' ') {
                valid_char = true;
            }
        }
        if (valid_char) {
            log_print_buffer[len - 1] = 0;
            // skip \n
            if (log_print_buffer[0] == '\n') {
                return 0;
            }
            LOGI("%s", log_print_buffer);
        }
    }
    return len;
}

void redirect_log() {
    // log redirect
    esp_log_set_vprintf(&vprintf_redirect_custom);
    // install new logging function
    esp_log_level_set("TAG", ESP_LOG_DEBUG);
    // write into log
    esp_log_write(ESP_LOG_DEBUG, "TAG", "text");
}

int init_log() {
    char log_name[32] = {0};
    int cur_name_id = 0;
    int history_id = 0;
    char id_buf[32] = {0};

    // redirect ESP_LOG() to LOGI()
    redirect_log();

    // get newest, history id, and round name id
    for (int i = 0; i < MAX_LOG_FILE_CNT; i++) {
        sprintf(log_name, "%s_%d.log", LOG_FILE_PRE, i);
        FILE *fp = fopen(log_name, "r");
        if (!fp) {
            printf("%s not found!\n", log_name);
            continue;
        } else {
            fgets(id_buf, 32, fp);  // header: "id:%d"
            int tmp = atoi(id_buf + strlen("id:"));
            printf("found id_buf:%s in %s\n", id_buf, log_name);
            if (history_id < tmp) {
                history_id = tmp;
                cur_name_id = i;
            }
            fclose(fp);
        }
    }
    printf("found history_id:%d cur_name_id:%d\n", history_id, cur_name_id);

    // open and create next log file
    CustomLog::createNewLogFile(history_id, cur_name_id);
    return 0;
}

int CustomLog::createNewLogFile(int pre_content_id, int pre_name_id) {
    // open and create next log file
    sprintf(gCustomLog.mpLogName, "%s_%d.log", LOG_FILE_PRE,
            (pre_name_id + 1) % MAX_LOG_FILE_CNT);
    if (gCustomLog.mpLogFD != nullptr) {
        gCustomLog.mInited = false;
        fclose(gCustomLog.mpLogFD);
        gCustomLog.mpLogFD = nullptr;
    }
    gCustomLog.mpLogFD = fopen(gCustomLog.mpLogName, "w+");
    if (gCustomLog.mpLogFD != NULL) {
        fprintf(gCustomLog.mpLogFD, "id:%d\n\n", pre_content_id + 1);
        printf("I create new log file:%s\n", gCustomLog.mpLogName);
        fprintf(gCustomLog.mpLogFD, "create new log file:%s\n",
                gCustomLog.mpLogName);
        // reset,after use new log file
        gCustomLog.mInited = true;
        gCustomLog.mContentId = pre_content_id + 1;
        gCustomLog.mNameId = (pre_name_id + 1) % MAX_LOG_FILE_CNT;
        gCustomLog.mLogSize = 0;
    } else {
        printf("E log file init error!\n");
        gCustomLog.mInited = false;
    }
    return 0;
}

int dump_log() {
    // get newest, history id, and round name id
    char log_name[32] = {0};
    for (int i = 0; i < MAX_LOG_FILE_CNT; i++) {
        sprintf(log_name, "%s_%d.log", LOG_FILE_PRE, i);
        printf("dump E:%s\n", log_name);
        print_file(log_name);
        printf("dump X:%s\n", log_name);
    }
    return 0;
}
